#include <event2/event.h>
#include <event2/listener.h>
#include <event2/bufferevent.h>
#include <ctype.h>
#include <string.h>
#include <unistd.h>
#include <cstdio>
#include <ctime>
#include <cstdlib>

#include <iostream>
#include <list>
#include <algorithm>

#include "main.h"
#include "data_process.h"
#include "snake_define.h"
#include "game_config.h"


extern std::list<Snake> snake_list;


class finder_t
{
    finder_t(struct bufferevent *bev)
    {
        m_bev = bev;
    }

    bool operator()(Snake s) 
    { 
        return (m_bev == s.get_snake_evp());
    }
    struct bufferevent *m_bev;
};
/*
*@func: bev对象的读回调，接收客户端写来的信息
*@desc: 对于贪吃蛇游戏而言，发送过来的应该是 ClientData 结构体(其中包含了命令和方向两个内容)
（应该尝试解析这个结构体）
*@param:struct bufferevent *bev 携带cfd信息的结构体
        void *arg 
*/
void read_cb(struct bufferevent *bev, void *arg)
{
    ClientData temp_cdata;
    int ret = 0;
    //从客户端中读取
    ret = bufferevent_read(bev, &temp_cdata, sizeof(temp_cdata));
    //Snake *snakep = (Snake *)arg; //此处因为list的push_back()复制作用，而不起效果。
    
    //首先查找
    auto iter = snake_list.begin();
    for(; iter!=snake_list.end(); iter++)
    {
        if(iter->get_snake_evp() == bev)
        {
            break;
        }
    }
    
    if(ret == sizeof(ClientData))//如果成功接收到了数据
    {
        //temp_cdata.parse_data(snakep); //先不进行处理
        temp_cdata.parse_data(*iter);
    }
    else if(ret == 0)//表示对端已经关闭，这时应该将这条蛇删去,并且清空相应的资源（bufferevent, snake_list)
    {
        //snake_list.remove(*snakep);

        std::cout << "A player is removed!" <<std::endl;
        //bufferevent_free(bev);
    }
    else if(ret < 0)//表示读取错误
    {
        perror("read failed!");
        exit(-1);
    }
}


void write_cb(struct bufferevent *bev, void *arg)
{
    printf("I'm 服务器,成功写数据给客户端了,写缓冲区回调函数被调用...\n");
}


/*
*@func:listener监听器的回调函数
*@desc: 1. 最多允许的玩家数目为4，因此设定一个房间为4; 挨个检查每个房间，找到可进行游戏的房间。记录room_no
            如果没有找到用户的房间，那么本次就不做处理（这样感觉不好，应该回写给客户端房间已满）
        2. 创建并初始化一条新的蛇，设置这条蛇的起点坐标等等。
        3. 然后将用户的套接字等相关信息（这里可能是struct bufferevent *bev）放进蛇对象的用户信息属性当中
        （其中包括了ip/port、socket_fd 等相关信息）
       
*/

bool room_aval[4] = {true, true, true, true};
char full_room_msg[] = "Room is full, you can enter the Game, Sorry!\n";
void listener_cb(struct evconnlistener *listener,
                 evutil_socket_t fd,
                 struct sockaddr *addr,
                 int len, void *ptr)
{
    printf("connect new client\n");

    // 接收监听器传进来的base
    struct event_base *base = (struct event_base *)ptr;

    // 创建bev对象,用于监听客户端的读写事件
    struct bufferevent *bev;
    bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);//相当于accpet

    //检查有没有空闲的房间（蛇的数量小于4）
    if(snake_list.size() >= 4)
    {
        //房间已经满了，不能再添加了。传给客户端错误信息，释放创建的bev
        bufferevent_write(bev, full_room_msg, strlen(full_room_msg)+1);
        bufferevent_free(bev);
        return;
    }
    else//说明还有空闲的房间
    {
        //Snake new_snake(bev,10,10,RIGHT, 5);//创建一条新的蛇(应该避免蛇创建在其他蛇存在的区域) 这个结构体在函数结束之后会销毁，对下面的set_cb参数有影响
        //Snake *new_snakep = new Snake(bev, 10, 10, RIGHT, 5);
        //此处应该修改，设置新的起点 Snake *new_snakep = new Snake(bev, 10, 10, RIGHT, 5);
        int new_head_y = 10*(snake_list.size()+1);
        //Snake *new_snakep = new Snake(bev, 10, new_head_y, RIGHT, 5);
        Snake *new_snakep = new Snake(bev);

        do//直到生成一条有效的蛇
        {
            new_snakep->build_head_body(10, new_head_y, RIGHT, 5);
            new_head_y += 5;
        }while(!new_snakep->check_birth_valid());

        snake_list.push_back(*new_snakep);//尽管push_back了，但是是一个副本
        std::cout << "A New Snake birth~~~" <<std::endl;
        SnakeNode new_head = new_snakep->get_snake_head();
        std::cout << "Snake's position " << new_head.x <<"," <<new_head.y <<std::endl;
        std::cout << "Snake's len " << new_snakep->get_snake_len() <<std::endl;
        // 设置bev对象的读写回调
        //bufferevent_setcb(bev, read_cb, write_cb, NULL, NULL);
        //bufferevent_setcb(bev, read_cb, NULL, NULL, (void *)new_snakep);//仅仅设置读回调,传递的参数为当前的蛇的地址
        //int no = snake_list.size()-1;
        bufferevent_setcb(bev, read_cb, NULL, NULL, (void *)new_snakep);//传递的参数为蛇的编号
        
        // 设置读的权限
        bufferevent_enable(bev, EV_READ);
    }

}


/*
*@func: 向用户发送地图线程
*@desc:需要用到全局变量  PicCode map[80][80];
*@param:void *arg：传入NULL
*/
//extern PicCode map[80][80];
extern Map global_map;
void* send_map(void* arg)
{    
    char a = 0;
    char b = 0;
    
    while(1)
    {
        //更新整体地图数据
        global_map.draw_food();
        global_map.draw_snake();
        // 向每一个蛇都写数据
        if(snake_list.size()!=0)
        {
            char test_str[] = "Server's teststr!\n";
            for(auto iter = snake_list.begin(); iter!= snake_list.end(); iter++)
            {
                if(!iter->isGameStarted())//如果当前蛇游戏还未开始(GameStat!= GAME_STARTED),那么不发送数据，跳过这条蛇
                {
                    continue;
                }
                //当GameStat == GAME_STARTED时（接收到 ClientData.cmd == START_GAME)，开始设置ServerData并发送
                ServerData sdata(iter->get_snake_score(), 0, NONE_CMD);
                #if 0
                ServerData sdata(a, b);//测试用，score->a; mvp->b //ServerData sdata(77, 88);//测试用
                a++;
                b++;
                if(a >100 ) a = 0;
                if(b > 100) b = 0;
                #endif 

                /*获取当前蛇的蛇头，设置客户地图偏移量后，并存入 ServerData 的地图对象*/
                int start_x = 0;
                int start_y = 0;
                SnakeNode temp_head = iter->get_snake_head();
                get_client_map_origin(temp_head.x, temp_head.y, &start_x, &start_y);
                global_map.set_client_map_region(sdata.get_map(), start_x, start_y);//存入 ServerData 地图对象

                //将这个数据发送给当前的客户端
                //bufferevent_write(iter->get_snake_evp(), test_str, strlen(test_str)+1);//先进行测试,测试成功了
                bufferevent_write(iter->get_snake_evp(), &sdata, sizeof(sdata)); //对于netcat而言，其能接收并显示的数据就是字符串类型的，此类型能否被接收未知
            }
        }
        
        //sleep(1);//每隔1s发送一次地图
        sleep(GAME_TICK);
    }
}


